// m_filter.go
package bmodel

import (
	"fmt"
	"reflect"
	"strconv"
	//"github.com/jinzhu/gorm"
)

//filter for input string convert, the top key correspond to one model, the top value means a interface{} within map[string][]interface{}
//  the inner key correspond to one field of one model, only id and something about time can neglect!
//  the inner value means an []interface{} with three things inside
//    first is a code type, 0: no check(text type), 1: check with maxlength limit(common varchar type), 2: check with valid convert(string convert to int by map[string]int, special int type), 3:check if it is valid int(common int type)
//  4: custom check with a function which must return an (int,error)(special int type, e.g. blockid,
//  5: custom check with a function which must return an (string, error)(special varchar type, e.g. username,
//    second is the check para, for 0, it equals nil; for 1, it equals the int val of maxlength, for 2, it equals a map[string]int for checking and type converting, for 3, it equals  a function pointer

var FORMAT_FILTER = map[string]map[string][]interface{}{
	"cmsuser": map[string][]interface{}{
		"Username": {0, nil, "cmsuser filter: NONE"},
		"Password": {0, nil, "cmsuser filter: NONE"},
	},
	"block": map[string][]interface{}{
		"Title":    {1, 70, "block filter: title too long"},
		"Parentid": {4, checkIfBlockparentidValid, "block filter: parentid invalid"},
		"Describe": {0, nil, "block filter: NONE"},
		"State":    {2, map[string]int{"debug": 0, "release": 1}, "block filter: state invalid"},
		"Reserve":  {1, 10000, "block filter: reverse too long"},
	},
	"article": map[string][]interface{}{
		"Blockid":  {4, checkIfArticleBlockidValid, "article filter: blockid check error"},
		"Priority": {3, nil, "article filter: priority is not a int type"},
		"Title":    {1, 70, "article filter: title too long"},
		"Keywords": {0, nil, "article filter: NONE"},
		"Brief":    {1, 500, "article filter: brief too long"},
		"Content":  {0, nil, "article filter: NONE"},
		"Type":     {3, nil, "article filter: type is not a int type"},
		"Remark":   {1, 128, "article filter: remark too long"},
		"State":    {2, map[string]int{"debug": 0, "release": 1}, "article filter: state invalid"},
		"Reserve":  {1, 10000, "article filter: reverse too long"},
	},
}

/*
 * first filter: convert all string input (map[string]string) to formated input map[string]interface{}
 * para: choose string, data &map[string]string, fdata *map[string]interface{}
 * return error
 */
func firstFilter(choose string, data map[string]string, fdata *map[string]interface{}) error {
	if formatTable, boolRet := FORMAT_FILTER[choose]; !boolRet {
		return fmt.Errorf(fmt.Sprintf("model para check: cannot find model type %s", choose))
	} else {
		for ikey, inputStr := range data {
			if ival, inputFind := formatTable[ikey]; !inputFind {
				return fmt.Errorf(fmt.Sprintf("model para check: data input %s cannot find in %s", ikey, choose))
			} else {

				if len(ival) == 0 || len(ival) > 3 {
					return fmt.Errorf(fmt.Sprintf("model para check: FORMAT_FILTER len([]interface{}) == 0 || len([]interface{}) > 3, of %s in %s", ikey, choose))
				}
				var errorStr string
				var errorOccur bool
				if errorStr, errorOccur = ival[2].(string); !errorOccur {
					return fmt.Errorf(fmt.Sprintf("model para check: FORMAT_FILTER , []interface{}[2] type is not a string, of %s in %s", ikey, choose))
				}

				checkCode, ok := ival[0].(int)
				if !ok {
					return fmt.Errorf(fmt.Sprintf("model para check: FORMAT_FILTER checkcode is not a int, of %s in %s", ikey, choose))
				}
				switch checkCode {
				case 0:
					{
						if ival[1] != nil {
							return fmt.Errorf(fmt.Sprintf("model para check: FORMAT_FILTER checkcontent type!=nil, of %s in %s", ikey, choose))
						}
						(*fdata)[ikey] = inputStr
					}
				case 1:
					{
						limitLength, innerok := ival[1].(int)
						if !innerok {
							return fmt.Errorf(fmt.Sprintf("model para check: FORMAT_FILTER checkcontent type!=int, of %s in %s", ikey, choose))
						}

						if len(inputStr) > limitLength {
							return fmt.Errorf(fmt.Sprintf("model para check: %s of %s in %s", errorStr, ikey, choose))
						}
						(*fdata)[ikey] = inputStr
					}
				case 2:
					{
						conMap, innerok := ival[1].(map[string]int)
						if !innerok {
							return fmt.Errorf(fmt.Sprintf("model para check: FORMAT_FILTER checkcontent type!=map[string]int, of %s in %s", ikey, choose))
						}

						var inputInt int
						var inputOccur bool
						if inputInt, inputOccur = conMap[inputStr]; !inputOccur {
							return fmt.Errorf(fmt.Sprintf("model para check: %s of %s in %s", errorStr, ikey, choose))
						}
						(*fdata)[ikey] = inputInt
					}
				case 3:
					{
						if ival[1] != nil {
							return fmt.Errorf(fmt.Sprintf("model para check: FORMAT_FILTER checkcontent type!=nil, of %s in %s", ikey, choose))
						}

						inputInt, innerErro := strconv.Atoi(inputStr)
						if innerErro != nil {
							return fmt.Errorf(fmt.Sprintf("model para check: %s of %s in %s", errorStr, ikey, choose))
						}
						(*fdata)[ikey] = inputInt
					}
				case 4:
					{
						conFunc, innerok := ival[1].(func(string) (int, error))
						if !innerok {
							return fmt.Errorf(fmt.Sprintf("model para check: FORMAT_FILTER checkcontent type!=func(string) (int, error), of %s in %s", ikey, choose))
						}

						if realInt, innerErro := conFunc(inputStr); innerErro != nil {
							return fmt.Errorf(fmt.Sprintf("model para check: custom func: %s, of %s of %s in %s", innerErro, errorStr, ikey, choose))
						} else {
							(*fdata)[ikey] = realInt
						}
					}
				case 5:
					{
						conFunc, innerok := ival[1].(func(string) (string, error))
						if !innerok {
							return fmt.Errorf(fmt.Sprintf("model para check: FORMAT_FILTER checkcontent type!=func(string) (string, error), of %s in %s", ikey, choose))
						}

						if realStr, innerErro := conFunc(inputStr); innerErro != nil {
							return fmt.Errorf(fmt.Sprintf("model para check: custom func: %s, of %s of %s in %s", innerErro, errorStr, ikey, choose))
						} else {
							(*fdata)[ikey] = realStr
						}
					}
				default:
					{
						return fmt.Errorf(fmt.Sprintf("model para check: FORMAT_FILTER checkcode out of range, of %s in %s", ikey, choose))
					}
				}
			}
		}
		return nil
	}
}

/*
 * second filter, convert formated input map[string]interface{} to specified struct
 * para: choose string, fdata map[string]interface{}, inStrcutPtr interface{}, the third thing musts be a pointer, func directedly set value to this strcut pointer
 * return error
 */
func secondFilter(choose string, fdata map[string]interface{}, inStrcutPtr interface{}) error {
	if _, boolRet := FORMAT_FILTER[choose]; !boolRet {
		return fmt.Errorf(fmt.Sprintf("model para check: cannot find model type %s", choose))
	} else {
		rType := reflect.TypeOf(inStrcutPtr)
		rVal := reflect.ValueOf(inStrcutPtr)

		if rType.Kind() != reflect.Ptr {
			return fmt.Errorf(fmt.Sprintf("model para check: when convert map to struct: input struct interface{} must be a pointer, choose is %s", choose))
		} else {
			rType = rType.Elem()
			rVal = rVal.Elem()
		}

		for i := 0; i < rType.NumField(); i++ {
			t := rType.Field(i)
			f := rVal.Field(i)

			var isPass bool = false
			for _, val := range PASSAWAY_FIELD {
				if val == t.Name {
					isPass = true
					break
				}
			}
			if !isPass {
				if v, ok := fdata[t.Name]; !ok {
					return fmt.Errorf(fmt.Sprintf("model para check: when convert map to struct: %s of struct not in fdata(map[string]interface{}), choose is %s", t.Name, choose))
				} else {
					f.Set(reflect.ValueOf(v))
				}
			}
		}
	}

	return nil //dump return
}
